using System.Reflection.Emit;
using Crux.Core.Dapper.Internals;
using Microsoft.Extensions.DependencyInjection;

namespace Crux.Core.Dapper;
/// <inheritdoc/>
public class DbContextFactory<TContext>(
        IOptions<DapperConfig<TContext>> options,
        IExecuteListener executeListener,
        IServiceProvider provider)
    : IDbContextFactory<TContext>
    where TContext : DapperContext
{
    private delegate TContext DynamicContextActivator(IDbConnection connection);
    private static DynamicContextActivator? _activator;
    private readonly IExecuteListener _listener = executeListener ?? throw new ArgumentNullException(nameof(executeListener));
    private readonly IOptions<DapperConfig<TContext>> _options = options ?? throw new ArgumentNullException(nameof(options));
    private readonly IServiceProvider _provider = provider ?? throw new ArgumentNullException(nameof(provider));
    /// <inheritdoc/>
    public TContext CreateDbContext()
    {
        var factory = _options?.Value?.Factory;
        if (factory is null)
        {
            throw new ArgumentException("DapperConfig.Factory is null");
        }

        return CreateDbContext(factory);
    }

    /// <inheritdoc/>
    public TContext CreateDbContext(Func<DbConnection> factory)
    {
        DbConnection wrappedConnection = new WrappedConnection(factory(), _listener);

        if (ConnectionState.Closed == wrappedConnection.State)
        {
            wrappedConnection.Open();
        }

        if (_activator is not null) return _activator(wrappedConnection);

        var connectionCtorType = new[] { typeof(IDbConnection) };

        var dynamicMethod = new DynamicMethod("CreateInstance", typeof(TContext), connectionCtorType, true);

        var ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.Emit(OpCodes.Nop);

        var ctor = typeof(TContext).GetConstructor(connectionCtorType);
        if (ctor is null)
        {
            throw new MissingMemberException("UnableToFindSessionConstructor");
        }

        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Newobj, ctor);
        ilGenerator.Emit(OpCodes.Ret);

        _activator = (DynamicContextActivator)dynamicMethod.CreateDelegate(typeof(DynamicContextActivator));

        return _activator(wrappedConnection);
    }

    /// <inheritdoc/>
    public IUnitOfWork<TContext> GetUnitOfWork(Func<DbConnection> factory)
    {
        var logger = provider.GetRequiredService<ILogger<UnitOfWork<TContext>>>();

        var uow = new UnitOfWork<TContext>(this, _options, logger);
        uow.SetContext(() => CreateDbContext(factory));

        return uow;
    }

    /// <inheritdoc/>
    public IQueryRepository<TContext> GetQueryRepository(Func<DbConnection> factory)
    {
        var logger = provider.GetRequiredService<ILogger<UnitOfWork<TContext>>>();
        var uow = new UnitOfWork<TContext>(this, _options, logger);
        uow.SetContext(() => CreateDbContext(factory));

        var repository = new QueryRepository<TContext>(uow, _options, _provider);

        return repository;
    }

}